package com.amadeus.spin.devtool.soap.shooter.config;

import java.util.ArrayList;
import java.util.Collection;

import org.apache.log4j.Logger;

import com.amadeus.spin.devtool.soap.shooter.FatalException;
import com.amadeus.spin.devtool.soap.shooter.process.AddDelayToOutput;
import com.amadeus.spin.devtool.soap.shooter.process.ForwardOutput;
import com.amadeus.spin.devtool.soap.shooter.process.MakePauseOnOutput;
import com.amadeus.spin.devtool.soap.shooter.process.Replace;
import com.amadeus.spin.devtool.soap.shooter.process.ReplaceInputVersion;
import com.amadeus.spin.devtool.soap.shooter.process.ReplaceSchema;
import com.amadeus.spin.devtool.soap.shooter.process.ReplaceSchemaVersion;
import com.amadeus.spin.devtool.soap.shooter.process.ReplaceVariable;
import com.amadeus.spin.devtool.soap.shooter.process.RetrieveHostTraces;
import com.amadeus.spin.devtool.soap.shooter.process.ValidateGrammar;
import com.amadeus.spin.devtool.soap.shooter.validate.ValidateOutput;

/**
 * Factory service: can be used to abstract the actual implementation of an
 * interface. Read the configuration file to find mappings between interfaces
 * and implementation. Make it easy to reuse processes replacing dynamically
 * part of these processes.
 *
 * <pre>
 * Expect the following settings in config:
 *   &amp;ltfactory&amp;gt
 *     &amp;ltclass name=&quot;ServiceInterface&quot;
 *        class=&quot;com.amadeus.spin...ServiceImplementation&quot;/&amp;gt
 *   &amp;lt/factory&amp;gt
 * </pre>
 */
public class Factory {

  private static Logger logger = Logger.getLogger(Factory.class);

  private Collection<Class<?>> impl = new ArrayList<Class<?>>();

  /**
   * Mechanism to allow adding new functionalities to the soapshooter. Has never
   * been used. We hardcode the interfaces/implementations list. We can now
   * remove the useless 'factory' tags from scenarios. A finer refactoring
   * should be done though.
   *
   * @throws FatalException
   *           initialisation issue.
   */
  public Factory() throws FatalException {
    try {
      impl.add(AddDelayToOutput.class);
      impl.add(ForwardOutput.class);
      impl.add(MakePauseOnOutput.class);
      impl.add(Replace.class);
      impl.add(ReplaceInputVersion.class);
      impl.add(ReplaceSchema.class);
      impl.add(ReplaceSchemaVersion.class);
      impl.add(ReplaceVariable.class);
      impl.add(RetrieveHostTraces.class);
      impl.add(ValidateGrammar.class);
      impl.add(ValidateOutput.class);
    }
    catch (Exception e) {
      logger.error("Failed to initialise factory", e);
      throw new FatalException("Failed to initialise factory", e);
    }
  }

  /**
   * Factory method, build an object instance based on a given name Will look
   * for the actual implementation, if found will build an instance using its
   * default constructor.
   *
   * @param iname
   *          the symbolic name of the interface.
   * @return an object implementing this interface
   * @throws Exception
   *           any kind, has been logged
   */
  public Object newInstance(String iname) throws FatalException {
    try {
      Class<?> c = getClass(iname);
      return c.newInstance();
    }
    catch (ClassNotFoundException e) {
      String logMessage = "Cannot find requested implementation: " + iname;
      logger.error(logMessage);
      logger.error("Check your <factory> entry in your scenario.");
      throw new FatalException(logMessage, e);
    }
    catch (InstantiationException e) {
      throw new FatalException("Unexpected Error while instanciating processor " + iname, e);
    }
    catch (IllegalAccessException e) {
      throw new FatalException("Unexpected Error while accessing processor " + iname, e);
    }
  }

  private Class<?> getClass(String iname) throws ClassNotFoundException {
    for (Class<?> c : impl) {
      if (c.getSimpleName().equals(iname)) {
        return c;
      }
    }
    throw new ClassNotFoundException(iname + ".class was not found");
  }
}